package lt.vu.mif.os;

import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import lt.vu.mif.os.commands.Commands;
import lt.vu.mif.os.memory.UserMemory;
import lt.vu.mif.os.memory.Word;
import lt.vu.mif.os.provider.Constants;
import lt.vu.mif.os.memory.VirtualMemory;
import lt.vu.mif.os.processors.VirtualMachineRegisters;
import lt.vu.mif.os.parser.Parser;
import lt.vu.mif.os.processes.Process;
//import java.awt.*;
import javax.swing.JOptionPane;
import lt.vu.mif.os.processors.RealMachineProcessor;
/**
* Virtual machine class which performs some specifics commands
* from loaded program.
* 
* @author Antanas Rudaitis
* @author Laurynas Paradauskas
* @author Algirdas Šaltupis
* 
*/
public class VirtualMachine extends Commands implements Process {
    public VirtualMachineRegisters registers;
    RealMachineProcessor rmp;
    boolean endOfProgram = false;
    Stack<Object> stack = new Stack<Object>();
    private Word[] uMem;
    private VMFrame vmFrame;
    private int cmdNr;
    private int id;
    private int state;
    private int rowInProcessList;
    //static Word[] memory;
    /**
     * 
     * @param uMem must be given in order to initialize virtual machine's memory
     * @param sf program's source file which will perfrom this virtual machine
     */
    public VirtualMachine(Word[] uMem, String sf, int id) {
        this.uMem = uMem;
        VirtualMemory.memory = uMem;
        this.id = id;
        vmFrame = new VMFrame(sf, uMem, this);
        registers = new VirtualMachineRegisters(this, (id-1)*Constants.vmMemSize);
       /* vmFrame.addWindowListener(new java.awt.event.WindowAdapter() {
          //kuomet VM uzdaroma cia dedam, kad VM counteri sumazina 1
        	public void windowClosing(java.awt.event.WindowEvent e) {
                UserMemory.resetToZero();
                RealMachine.rmFrame.updateRMMemoryState();
            }
        });
        vmFrame.setVisible(true);
       updateGUIReg();*/
        //this.rowInProcessList = RealMachine.rmFrame.addNewProcess(this.toString(), state);
        state = Constants.IDLE;
        rmp = new RealMachineProcessor();
    }
    /**
     * Method which updates GUI register parameter
     */
    public void updateGUIReg() {
    	//vmFrame.updatePC(Integer.toString(registers.getPc() - 1));
    	//vmFrame.updateSP(Integer.toString(stack.size()-1));
    }
    /**
     * Virtual machine's method which performs a 'step' in program
     */
    public void step() {
    	//System.out.println(VirtualMemory.memory[registers.getPc()].get());
    	if (!this.endOfProgram) {
	    	cmdNr = Parser.parse(UserMemory.memory[registers.getPc()].get());
	    	executeCmd(cmdNr);
	    	if (Constants.vmMemCodeSize != registers.getPc() && state == Constants.RUNNING) {
	    		if (state == Constants.RUNNING) {
	    			registers.setPc(registers.getPc()+1);
	    			System.out.println("atejo");
	    		}
	    	}
    	}
    }
    /**
     * Performs mathematical addition command with two top stack elements,
     * result puts into top of stack and decreases stack pointer by one.
     * [SP � 1] = [SP � 1] + [SP]; SP--;
     */
    public void add(){
        int temp = (Integer)stack.pop()+(Integer)stack.pop();
        stack.push(temp);
        System.out.println("ADD");
    }
    /**
     * Performs mathematical substract second stack element from first,
     * result puts into top of stack and decreases stack pointer by one.
     * [SP � 1] = [SP � 1] - [SP]; SP--;
     */  
    public void sub(){
        int temp1 = (Integer)stack.pop();
        int temp2 = (Integer)stack.pop();
        int temp = temp2-temp1;
        stack.push(temp);
        System.out.println("SUB");
    }
    /**
     * Performs multiplication with two top stack elements,
     * result puts into top of stack and decreases stack pointer by one.
     * [SP � 1] = [SP � 1] * [SP]; SP--;
     */
    public void mul(){
        int temp = Integer.parseInt(((String)stack.pop()).trim())*Integer.parseInt(((String)stack.pop()).trim());
        stack.push(temp);
        System.out.println("MUL");
    }
    /**
     * Divides top stack element from second,
     * result puts into top of stack and decreases stack pointer by one.
     * [SP � 1] = [SP � 1] / [SP]; SP--;
     */
    public void div(){
    	int temp = 0;
        int temp1 = Integer.parseInt(((String)stack.pop()).trim());
        int temp2 = Integer.parseInt(((String)stack.pop()).trim());
        if (temp1 != 0) { temp = temp2/temp1; }
        else {
        	RealMachine.setMode(true);
        	RealMachine.setPI((byte)2);
        	this.halt();
        }
        stack.push(temp);
        System.out.println("DIV");
    }
    /**
     * Increments top stack element.
     */
    public void inc(){
        int temp = Integer.parseInt(((String)stack.pop()).trim());
        temp++;
        stack.push(temp);
        System.out.println("INC");
    }

    public int getId() {
        return id;
    }
    
    /**
     * Decrements top stack element.
     */
    public void dec(){
        int temp = Integer.parseInt(((String)stack.pop()).trim());
        temp--;
        stack.push(temp);
        System.out.println("DEC");
    }

    /**
     * Compares two top stack elements and puts result into top of stack.
     * 1 - if equal, 0 - if top element lower, 2 - if greater
     */
    public void cmp(){
        int temp1 = Integer.parseInt(((String)stack.pop()).trim());
        int temp2 = Integer.parseInt(((String)stack.pop()).trim());
        if(temp1>temp2){
            stack.push(2);
        }
        else if(temp1<temp2){
            stack.push(0);
        }
        else{
            stack.push(1);
        }
        System.out.println("CMP");
    }
    /**
     * Loads data from memory cell with address 16 * x + y;
     * @param x
     * @param y
     */
    public void ld(int x, int y){
        stack.push(UserMemory.memory[Constants.vmMemSize*(id-1)+Constants.vmMemCodeSize + x * Constants.blockSize + y].get());
        System.out.println("LD");
    }    /**
     * Puts data from stack to memory cell with address 10 * x + y;
     * @param x
     * @param y
     */
    public void pt(int x, int y){
        String temp = Integer.toString((Integer)stack.pop());
        UserMemory.memory[Constants.vmMemSize*(id-1)+Constants.vmMemCodeSize + x * Constants.blockSize + y].set(temp);
        System.out.println("PT");
    }
    /**
     * Puts data into stack.
     * @param x
     */
    public void pun(int x){
        stack.push(x);
        System.out.println("PUN");
    }
	/**
	 * Jumps to code line: x * 10 + y
	 * @param x
	 * @param y
	 */
    public void jp(int x, int y){
        registers.setPc((id-1)*Constants.vmMemSize+x * Constants.blockSize + y - 1);
        System.out.println("JP");
    }
	/**
	 * Jumps to code line: x * 10 + y if top stack element equals 1.
	 * @param x
	 * @param y
	 */
    public void je(int x, int y){
        if((Integer)stack.pop()==1){
            registers.setPc((id-1)*Constants.vmMemSize+x * Constants.blockSize + y - 1);
        }
        System.out.println("JE");
    }
	/**
	 * Jumps to code line: x * 10 + y if top stack element equals 0.
	 * @param x
	 * @param y
	 */
    public void jl(int x, int y){
        if((Integer)stack.pop()==0){
            registers.setPc((id-1)*Constants.vmMemSize+x * Constants.blockSize + y - 1);
        }
        System.out.println("JL");
    }
	/**
	 * Jumps to code line: x * 10 + y if top stack element equals 2.
	 * @param x
	 * @param y
	 */
    public void jg(int x, int y){
        if((Integer)stack.pop()==2){
            registers.setPc((id-1)*Constants.vmMemSize+x * Constants.blockSize + y - 1);
        }
        System.out.println("JG");
    }
    /**
     * Stops virtual machine.
     */
    public void halt(){
        this.endOfProgram = true;
        this.state = Constants.HALT;
        RealMachine.decVMCounter();
        System.out.println("HALT!"); 
    }

	/* INPUT/OUTPUT */
    /**
     * Prints out top stack element.
     */
    public void prnt(){
        RealMachine.print("VM"+Integer.toString(id)+": "+stack.pop().toString());
//    	vmFrame.updateScreen(stack.pop().toString());
    	System.out.println("PRNT");
    }
    /**
     * Prints out elements from XY to XZ cells
     */
    public void p(int x, int y, int z){
      String str = "";
        for(int i=y;i<=z;i++){
            str = str + UserMemory.memory[(id-1)*Constants.vmMemSize+Constants.vmMemCodeSize + x * Constants.blockSize + i].get();
        }
//      vmFrame.updateScreen(str);
        RealMachine.print("VM"+Integer.toString(id)+": "+str);
        System.out.println("P");
    }
    /**
     * Reads input data and puts to stack.
     */
    public void read(){
    	RealMachine.setMode(true);
    	RealMachine.setSI((byte)1);
        RealMachine.rmFrame.updateRMGUI();
        RealMachine.rmFrame.setInput(true);
        
        
        RealMachine.setTimer(10);
        RealMachine.rmFrame.updateRMGUI();
        RealMachine.rmFrame.inputStartTime = System.currentTimeMillis();
        RealMachine.rmFrame.inputOn = true;
        //TODO sutvarkyti taimeri
          
//        if (RealMachine.rmFrame.inputOn)
//            stack.push((String)RealMachine.getFromStack());
//        else {
//            state = Constants.HALT;
//            this.endOfProgram = true;
//        }
        System.out.println("READ");
        RealMachine.setMode(false);
    	RealMachine.setSI((byte)0);
        RealMachine.rmFrame.updateRMGUI();
    }
    
    public void addToStack(String txt) {
    	stack.push(txt);
    }
    /**
     * Method which deals with NON commands and are excluded with %% tag in program.
     * @param x
     * @param y
     * @param str
     */
    public void strng(int x, int y, String str) {
        UserMemory.memory[(id-1)*Constants.vmMemSize+Constants.vmMemCodeSize + x * Constants.blockSize + y].set(str);
        if (state == Constants.RUNNING)
        	registers.setPc(registers.getPc()+1);
        else {
        	if (state != Constants.HAVING_PROCESSOR) {
        		state = Constants.ASKING_FOR_PROCESSOR;
        		
        	}
        	
        }
    }
    
    public void undefinied() {
    	RealMachine.setMode(true);
    	RealMachine.setPI((byte)1);
    	this.halt();
    }
    /**
     * Executes identified command
     * @param cNr Command number
     */
    private void executeCmd(int cNr) {
    	switch (cNr) {
    	case 0: undefinied(); break;
    	case 1: 
            if (state == Constants.RUNNING)
                add();
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
    	
        case 2: 
            if (state == Constants.RUNNING)
                sub(); 
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
    	
        case 3: 
            if (state == Constants.RUNNING)
                mul(); 
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
    	
        case 4: 
            if (state == Constants.RUNNING)
                div(); 
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 5: 
            if (state == Constants.RUNNING)
                cmp(); 
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 6: 
            if (state == Constants.RUNNING)
                ld(UserMemory.memory[registers.getPc()].get().charAt(2) - '0',UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 7: 
            if (state == Constants.RUNNING)
                pt(UserMemory.memory[registers.getPc()].get().charAt(2)  - '0', UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 8: 
            if (state == Constants.RUNNING)
                pun(UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 9: 
            if (state == Constants.RUNNING)
                jp(UserMemory.memory[registers.getPc()].get().charAt(2)  - '0', UserMemory.memory[registers.getPc()].get().charAt(3)  - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 10: 
            if (state == Constants.RUNNING)
                je(UserMemory.memory[registers.getPc()].get().charAt(2) - '0', UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 11: 
            if (state == Constants.RUNNING)
                jl(UserMemory.memory[registers.getPc()].get().charAt(2) - '0', UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }    
            break;
            
    	case 12: 
            if (state == Constants.RUNNING)
                jg(UserMemory.memory[registers.getPc()].get().charAt(2) - '0', UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }  
            break;
            
    	case 13: 
            if (state == Constants.RUNNING) {
                halt(); 
                state = Constants.HALT;
            }
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 14: 
            if (state == Constants.RUNNING)
                prnt(); 
            else {
                state = Constants.ASKING_FOR_OUTPUT;
                RealMachine.console.addToQueue(this);
            }
            break;
            
    	case 15: 
            if (state == Constants.RUNNING)
                p(UserMemory.memory[registers.getPc()].get().charAt(1) - '0', UserMemory.memory[registers.getPc()].get().charAt(2) - '0',UserMemory.memory[registers.getPc()].get().charAt(3) - '0');
            else {
                state = Constants.ASKING_FOR_OUTPUT;
                RealMachine.console.addToQueue(this);
            }
            break;
            
    	case 16: 
            if (state == Constants.RUNNING)
                read(); 
            else {
                state = Constants.ASKING_FOR_INPUT;
                RealMachine.input.addToQueue(this);
            }
            break;
            
    	case 17: 
            if (state == Constants.RUNNING)
                inc(); 
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 18: 
            if (state == Constants.RUNNING)
                dec(); 
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
            break;
            
    	case 19: 
            if (state == Constants.RUNNING)
                strng(UserMemory.memory[registers.getPc()].get().charAt(2) - '0',UserMemory.memory[registers.getPc()].get().charAt(3) - '0',
    			UserMemory.memory[registers.getPc() + 1].get());
            else {
                state = Constants.ASKING_FOR_PROCESSOR;
                RealMachine.processor.addToQueue(this);
            }
        break;
    	}
    }

    @Override
    public int getState() {
        return state;
    }

    @Override
    public void setState(int state) {
        this.state = state;
    }

    public RealMachineProcessor getRmp() {
        return rmp;
    }

    public void setRmp(RealMachineProcessor rmp) {
        this.rmp = rmp;
    }
}
